/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2010.

	This file is part of the Maxsi Library.

	Maxsi Library is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi Library is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
	License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Library. If not, see <http://www.gnu.org/licenses/>.

	Maxsi Library
	A powerful Cross-Platform C++ General Purpose Library that allows you to
	efficiently create high-performance and reliable applications.

	MaxsiFileSystem.cpp
	Useful functions for filesystem interaction.

******************************************************************************/

#include "MaxsiLibrary.h"

#ifdef Maxsi_Support_W32
#include <direct.h>
#endif

#ifdef Maxsi_Support_POSIX
#include <dirent.h> 
#endif

BeginMaxsiNamespace

#if defined(WIN32)
MaxsiFileSystemWindows
#elif defined(Maxsi_Support_POSIX)
MaxsiFileSystemPOSIX
#endif
/*IFileSystem*/ LocalFileSystem;

IFileSystem* FileSystem()
{
	return (IFileSystem*)&LocalFileSystem;
}

IFileSystem* AppFiles()
{
	return (IFileSystem*)NULL;
}

IFileSystem* TempFiles()
{
	return (IFileSystem*)NULL;
}

//=============================================================================
//	MaxsiFileSystemPOSIX - Implements IFileSystem for POSIX.
//=============================================================================
#ifdef Maxsi_Support_POSIX

MAXSI_DEFINE_IMPLEMENTATION(MaxsiFileSystemPOSIX, IFileSystem, MAXSI_INTERFACE_STATIC);

MaxsiError MaxsiFileSystemPOSIX::GetErrorCode() // WARNING! NOT THREAD-SAFE!
{
	return ErrorCode;
}

MaxsiError MaxsiFileSystemPOSIX::SetErrorCode(MaxsiError NewErrorCode) // WARNING! NOT THREAD-SAFE!
{
	ErrorCode	=	ErrorCode;

	return ErrorCode;
}

MaxsiHandle MaxsiFileSystemPOSIX::OpenFile(MESTR* Path, FLAGS32 Mode)
{
	// Convert the Path to the system locale!
	char*	PathSL	=	MESTR2CHAR(Path);

	// Check for error conditions.
	if ( PathSL == NULL ) { return NULL; }	

	// Allocate handlefull of int.
	int*	Handle	=	new int;

	// Check for error conditions.
	if ( Handle == NULL ) { delete[] PathSL; return NULL; }

	int		Flags	=	O_LARGEFILE;

	if ( (Mode & MAXSI_FILE_READ) && !(Mode & MAXSI_FILE_WRITE) ) { Flags |= O_RDONLY; }
	if ( !(Mode & MAXSI_FILE_READ) && (Mode & MAXSI_FILE_WRITE) ) { Flags |= O_WRONLY; }
	if ( (Mode & MAXSI_FILE_READ) && (Mode & MAXSI_FILE_WRITE) ) { Flags |= O_RDWR; }
	if ( Mode & MAXSI_FILE_CREATE ) { Flags |= O_CREAT; }
	if ( Mode & MAXSI_FILE_TRUNCATE ) { Flags |= O_TRUNC; }
	// Not available under Linux: if ( Mode & MAXSI_FILE_SEQUENTIAL ) { Flags |= FILE_FLAG_SEQUENTIAL_SCAN; }

	int Permissions = 0;
	
	if ( Mode & MAXSI_FILE_CREATE )
	{
		MAXSI_TODO("Currently we are granting 777 to any newly created file! Is that what we want?");
	
		Permissions |= S_IRWXU; // User (file owner) has read, write and execute permission 
		Permissions |= S_IRWXG; // Group has read, write and execute permission 
		Permissions |= S_IRWXO; // Others have read, write and execute permission
	}

	*Handle			=	open(PathSL, Flags, Permissions);

	delete[] PathSL;

	if ( *Handle == -1 ) { delete Handle; SetErrorCode(1); return NULL; }

	SetErrorCode(0);

	return (MaxsiHandle)Handle;
}

size_t MaxsiFileSystemPOSIX::ReadFile(MaxsiHandle FileHandle, BYTE* Buffer, size_t Size)
{
	size_t		Left	=	Size;
	ssize_t		Read	=	1;

	while ( Left && Read )
	{
		Read	=	read(*((int*)FileHandle), Buffer+(Size-Left), Left);

		if ( Read == -1 || Read > SSIZE_MAX ) { SetErrorCode(1); return 0; }

		if ( Read < 0 ) { Read = 0; } MAXSI_TODO("Test if this is correct behavior!");

		Left	-=	(size_t)Read;	
	}
	
	SetErrorCode(0);

	return (size_t)(Size-Left);
}

size_t MaxsiFileSystemPOSIX::WriteFile(MaxsiHandle FileHandle, BYTE* Buffer, size_t Size)
{
	size_t		Left	=	Size;

	while ( Left )
	{
		ssize_t		Written		=	write(*((int*)FileHandle), Buffer+(Size-Left), Left);

		if ( Written == -1 )
		{
			int Error = errno;
			SetErrorCode(Error);
			return 0;
		}

		if ( Written < 0 ) { Written = 0; } MAXSI_TODO("Test if this is correct behavior!");

		Left	-=	(size_t)Written;
	}

	SetErrorCode(0); 

	return Size;
}

bool MaxsiFileSystemPOSIX::SeekFile(MaxsiHandle FileHandle, FileSize_t Offset)
{
	if ( lseek64(*(int*)FileHandle, (off64_t)Offset, SEEK_SET ) == (off64_t)-1 )
	{
		int ErrorNumber = errno;
		SetErrorCode(ErrorNumber);
		return false;
	}

	SetErrorCode(0); 
	return true;
}

bool MaxsiFileSystemPOSIX::SetFileSize(MaxsiHandle FileHandle, FileSize_t FileSize)
{
	if ( ftruncate64(*(int*)FileHandle, (off64_t)FileSize) == -1 )
	{
		int ErrorNumber = errno;
		SetErrorCode(ErrorNumber);
		return false;
	}

	SetErrorCode(0);
	return true;
}

FileSize_t MaxsiFileSystemPOSIX::GetFileSize(MaxsiHandle FileHandle)
{
	struct	stat64	FileInfo;

	if ( fstat64(*(int*)FileHandle, &FileInfo) == -1 ) { SetErrorCode(1); return 0; }

	SetErrorCode(0);

	return (FileSize_t)FileInfo.st_size;
}

bool MaxsiFileSystemPOSIX::CloseFile(MaxsiHandle FileHandle)
{
	close(*(int*)FileHandle);

	delete (int*)FileHandle;

	return true;
}


bool MaxsiFileSystemPOSIX::CreateFolder(MESTR* Path)
{
	// Convert the Path to the system locale!
	char*	PathSL	=	MESTR2CHAR(Path);

	// Check for error conditions.
	if ( PathSL == NULL ) { return false; }	

	MAXSI_TODO_LOW("I am not sure if these permissions are correct!");

	bool	Result	= (mkdir(PathSL, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)==0);

	delete[] PathSL;

	return Result;
}


bool MaxsiFileSystemPOSIX::CompareFileNames(MESTR* Path1, MESTR* Path2)
{
	MAXSI_TODO("This function is way too naive. We need to add support for the current dir, any .. sequence, and all other sorts of fun stuff UNIX implements.");	

	return ( MESTRCMP(Path1, Path2) == 0 );
}

MaxsiError MaxsiFileSystemPOSIX::OpenFolder(MESTR* FolderName, MaxsiDirectoryEntry* Entry)
{
	if ( FolderName == NULL || Entry == NULL ) { return MAXSI_ERROR_INVALID_INPUT; }

	char* FolderNameSL	=	MESTR2CHAR(FolderName);

	if ( FolderNameSL == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	dirent64* DirEntry = new dirent64;

	if ( DirEntry == NULL ) { delete[] FolderNameSL; return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	DIR* Directory = opendir(FolderNameSL);

	delete[] FolderNameSL;

	if ( Directory == NULL ) { delete[] DirEntry; return MAXSI_ERROR_COULD_NOT_OPEN; }

	Entry->InternalP1 = (void*)Directory;
	Entry->InternalP2 = (void*)DirEntry;
	Entry->FileName = NULL;

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiFileSystemPOSIX::ReadFolder(MaxsiDirectoryEntry* Entry)
{
	IFDEL(Entry->FileName);

	DIR* 		Directory			=	(DIR*)Entry->InternalP1;
	dirent64*	DirectoryEntry		=	(dirent64*)Entry->InternalP2;
	dirent64*	DirectoryResult		=	NULL;

	if ( readdir64_r(Directory, DirectoryEntry, &DirectoryResult) != 0 ) { return MAXSI_ERROR_UNSPECIFIED; }

	if ( DirectoryResult == NULL ) { return MAXSI_ERROR_END_OF_STREAM; }

#ifdef _DIRENT_HAVE_D_TYPE
	// Detect the type of the directory. This is non-POSIX functionallity implemented by many projects, such as GNU.
	if ( DirectoryResult->d_type == DT_REG ) { Entry->Type = MAXSI_DIR_ENTRY_FILE; } else
	if ( DirectoryResult->d_type == DT_DIR ) { Entry->Type = MAXSI_DIR_ENTRY_DIR; } else
	if ( DirectoryResult->d_type == DT_UNKNOWN ) { Entry->Type = MAXSI_DIR_ENTRY_UNKNOWN; } else
	{ Entry->Type = MAXSI_DIR_ENTRY_OTHER;  }
#else
	Entry->Type			=	MAXSI_DIR_ENTRY_UNKNOWN;
#endif

	Entry->FileName		=	CHAR2MESTR(DirectoryResult->d_name);

	if ( Entry->FileName == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }



	return MAXSI_ERROR_SUCCESS;
}

void MaxsiFileSystemPOSIX::CloseFolder(MaxsiDirectoryEntry* Entry)
{
	// Delete any temporary filenames.
	IFDEL(Entry->FileName);

	DIR* 		Directory			=	(DIR*)Entry->InternalP1;
	dirent64*	DirectoryEntry		=	(dirent64*)Entry->InternalP2;

	// Close the dir
	closedir(Directory);

	delete DirectoryEntry;

	// Point of no return.
}

#endif

//=============================================================================
//	MaxsiFileSystemWindows - Implements IFileSystem for Microsoft Windows.
//=============================================================================
#ifdef Maxsi_Support_W32

MAXSI_DEFINE_IMPLEMENTATION(MaxsiFileSystemPOSIX, IFileSystem, MAXSI_INTERFACE_STATIC);

MaxsiError MaxsiFileSystemWindows::GetErrorCode() // WARNING! NOT THREAD-SAFE!
{
	return ErrorCode;
}

MaxsiError MaxsiFileSystemWindows::SetErrorCode(MaxsiError NewErrorCode) // WARNING! NOT THREAD-SAFE!
{
	ErrorCode	=	ErrorCode;

	return ErrorCode;
}

MaxsiHandle MaxsiFileSystemWindows::OpenFile(MESTR* Path, FLAGS32 Mode)
{
	DWORD	Access		=	0;
	DWORD	Creation	=	OPEN_EXISTING;
	DWORD	Attributes	=	FILE_ATTRIBUTE_NORMAL;

	if ( Mode & MAXSI_FILE_READ ) { Access |= GENERIC_READ; }
	if ( Mode & MAXSI_FILE_WRITE ) { Access |= GENERIC_WRITE; };
	if ( Mode & MAXSI_FILE_CREATE ) { Creation = OPEN_ALWAYS; }
	if ( Mode & MAXSI_FILE_TRUNCATE ) { Creation = TRUNCATE_EXISTING; }
	if ( Mode & MAXSI_FILE_SEQUENTIAL ) { Attributes |= FILE_FLAG_SEQUENTIAL_SCAN; }

	HANDLE Result = CreateFileM(Path, Access, FILE_SHARE_READ, NULL, Creation, Attributes, NULL);

	if ( Result == INVALID_HANDLE_VALUE ) { return NULL; }

	return (MaxsiHandle)Result;
}

size_t MaxsiFileSystemWindows::ReadFile(MaxsiHandle FileHandle, BYTE* Buffer, size_t Size)
{
	DWORD	BytesRead;

	BOOL	Result	=	::ReadFile((HANDLE)FileHandle, Buffer, (DWORD)Size, &BytesRead, NULL);

	if ( Result )
	{
		return (size_t)BytesRead;
	} 
	else
	{
		return 0;
	}
}

size_t MaxsiFileSystemWindows::WriteFile(MaxsiHandle FileHandle, BYTE* Buffer, size_t Size)
{
	DWORD	BytesWritten;

	BOOL	Result	=	::WriteFile((HANDLE)FileHandle, Buffer, (DWORD)Size, &BytesWritten, NULL);

	if ( Result )
	{
		return (size_t)BytesWritten;
	} 
	else
	{
		return 0;
	}
}

bool MaxsiFileSystemWindows::SeekFile(MaxsiHandle FileHandle, FileSize_t Offset)
{
	MAXSI_TODO("There is no 64-bit support here, as files over 2^32 bytes will fail!");

	return (SetFilePointer((HANDLE)FileHandle, (LONG)Offset, NULL, FILE_BEGIN) != INVALID_SET_FILE_POINTER);
}

bool MaxsiFileSystemWindows::SetFileSize(MaxsiHandle FileHandle, FileSize_t FileSize)
{
	if ( SeekFile(FileHandle, FileSize) == false ) { return false; }

	return (SetEndOfFile((HANDLE)FileHandle) != 0);
}

FileSize_t MaxsiFileSystemWindows::GetFileSize(MaxsiHandle FileHandle)
{
	// All this pointer non-sense is because of how GCC handles strict aliasing, whatever it is.

	FileSize_t	Result;

	DWORD*		ResultPointerL	=	(DWORD*)&Result;
	DWORD*		ResultPointerH	=	(DWORD*)&Result;


	ResultPointerH++;

	DWORD		Lower			=	::GetFileSize(FileHandle, ResultPointerH);

	memcpy(ResultPointerL, &Lower, sizeof(Lower));


	return Result;
}

bool MaxsiFileSystemWindows::CloseFile(MaxsiHandle FileHandle)
{
	CloseHandle((HANDLE)FileHandle);

	return true;
}


bool MaxsiFileSystemWindows::CreateFolder(MESTR* Path)
{
	MAXSI_TODO("_mkdir on Windows only creates one folder per call! Please add support for creating nested dirs by looping and such!");

	return (CreateFolderActual(Path) != -1);
}


bool MaxsiFileSystemWindows::CompareFileNames(MESTR* Path1, MESTR* Path2)
{
	MAXSI_TODO("This function is way too naive. We need to add support for the current dir, any .. sequence, and all other sorts of fun stuff Windows implements.");	

	return (MESTRICMP(Path1, Path2)==0);
}

#endif


MaxsiHandle MaxsiOpenFile(MESTR* Path, FLAGS32 Mode)
{
	return FileSystem()->OpenFile(Path, Mode);
}

size_t MaxsiReadFile(MaxsiHandle FileHandle, BYTE* Buffer, size_t Size)
{
	return FileSystem()->ReadFile(FileHandle, Buffer, Size);
}

size_t MaxsiWriteFile(MaxsiHandle FileHandle, BYTE* Buffer, size_t Size)
{
	return FileSystem()->WriteFile(FileHandle, Buffer, Size);
}

bool MaxsiSeekFile(MaxsiHandle FileHandle, FileSize_t Offset)
{
	return FileSystem()->SeekFile(FileHandle, Offset);
}

bool MaxsiSetFileSize(MaxsiHandle FileHandle, FileSize_t Size)
{
	return FileSystem()->SetFileSize(FileHandle, Size);
}

FileSize_t MaxsiGetFileSize(MaxsiHandle FileHandle)
{
	return FileSystem()->GetFileSize(FileHandle);
}

bool MaxsiCloseFile(MaxsiHandle FileHandle)
{
	return FileSystem()->CloseFile(FileHandle);
}

bool CompareFileNames(MESTR* File1, MESTR* File2)
{
	return FileSystem()->CompareFileNames(File1, File2);		
}

#if 0

MaxsiHandle MaxsiOpenFile(MESTR* Path, FLAGS32 Mode)
{
	DWORD	Access		=	0;
	DWORD	Creation	=	OPEN_EXISTING;
	DWORDS	Attributes	=	FILE_ATTRIBUTE_NORMAL;

	if ( Mode & MAXSI_FILE_READ ) { Access |= GENERIC_READ; }
	if ( Mode & MAXSI_FILE_WRITE ) { Access |= GENERIC_WRITE; }; }
	if ( Mode & MAXSI_FILE_CREATE ) { Creation = OPEN_ALWAYS; }
	if ( Mode & MAXSI_FILE_TRUNCATE ) { Creation = TRUNCATE_EXISTING; }
	if ( Mode & MAXSI_FILE_SEQUENTIAL ) { Attributes |= FILE_FLAG_SEQUENTIAL_SCAN; }

	return (MaxsiHandle)CreateFileM(Path, Access, FILE_SHARE_READ, NULL, Creation, Attributes, NULL);
}

size_t MaxsiReadFile(MaxsiHandle FileHandle, BYTE* Buffer, size_t Size)
{
	DWORD	BytesRead;

	BOOL	Result	=	ReadFile((HANDLE)FileHandle, Buffer, (DWORD)Size, &BytesRead, NULL);

	if ( Result )
	{
		return (size_t)BytesRead;
	} 
	else
	{
		return 0;
	}
}

size_t MaxsiWriteFile(MaxsiHandle FileHandle, BYTE* Buffer, size_t Size)
{
	return 0;
}

bool MaxsiSeekFile(MaxsiHandle FileHandle, FileSize_t Offset)
{
	return false;
}

bool MaxsiSetFileSize(MaxsiHandle FileHandle)
{
	return false;
}

FileSize_t MaxsiGetFileSize(MaxsiHandle FileHandle)
{
	return 0;
}

bool MaxsiCloseFile(MaxsiHandle FileHandle)
{
	return (CloseHandle((HANDLE)FileHandle) != FALSE);
}

FileSize_t GetFileSize64(HANDLE FileHandle)
{
	// All this pointer non-sense is because of how GCC handles strict aliasing, whatever it is.

	FileSize_t	Result;

	DWORD*		ResultPointerL	=	(DWORD*)&Result;
	DWORD*		ResultPointerH	=	(DWORD*)&Result;




	ResultPointerH++;

	DWORD		Lower			=	GetFileSize(FileHandle, ResultPointerH);

	memcpy(ResultPointerL, &Lower, sizeof(Lower));

	return Result;
}

#endif

int WriteFileSimple(MESTR* Path, BYTE* Data, size_t DataSize)
{
	if ( Path == NULL ) { return 1; }

	MaxsiHandle		FileHandle	=	FileSystem()->OpenFile(Path, MAXSI_FILE_WRITE | MAXSI_FILE_SEQUENTIAL | MAXSI_FILE_CREATE);
		
	if ( FileHandle == NULL )
	{
		return 1;
	}
	else
	{
		FileSystem()->SetFileSize(FileHandle, DataSize);

		size_t	Written		=	FileSystem()->WriteFile(FileHandle, Data, DataSize);
		
		if ( Written == 0 && DataSize != 0 ) { return 1; } // Failure

		FileSystem()->CloseFile(FileHandle);
	}

	return 0;
}

MaxsiError ReadFileSimple(MESTR* Path, BYTE** Data, size_t* DataSize)
{
	if ( Path == NULL ) { return MAXSI_ERROR_INVALID_INPUT; }

	MaxsiError	Result		=	MAXSI_ERROR_UNSPECIFIED;
	
	MaxsiHandle	FileHandle	=	FileSystem()->OpenFile(Path, MAXSI_FILE_READ | MAXSI_FILE_SEQUENTIAL);
	
	if ( FileHandle == NULL )
	{
		Result	=	MAXSI_ERROR_COULD_NOT_OPEN;
	}
	else
	{
		FileSize_t	FileSize		=	FileSystem()->GetFileSize(FileHandle);
		
		if ( FileSize > SIZE_MAX )
		{
			Result	=	MAXSI_ERROR_FILE_TOO_BIG;
		}
		else
		{
			if ( FileSize == 0 )
			{
				Result			=	MAXSI_ERROR_SUCCESS;
				*Data			=	NULL;
				*DataSize		=	0;
			}
			else
			{
				BYTE*	Buffer			=	new BYTE[FileSize];

				if ( Buffer != NULL )
				{
					size_t		ReadResult	=	FileSystem()->ReadFile(FileHandle, Buffer, (size_t)FileSize);
				
					if ( ReadResult != 0 )
					{
						Result				=	MAXSI_ERROR_SUCCESS;
						*Data				=	Buffer;
						*DataSize			=	FileSize;
					}
					else

					{
						delete[]	Buffer;	// Failure
					}
				}
				else
				{
					Result	=	MAXSI_ERROR_MEM_ALLOC_FAIL;
				}
			}
		}

		FileSystem()->CloseFile(FileHandle);
	}

	return Result;
}

MaxsiError ReadFileSimpleNullTerminated(MESTR* Path, BYTE** Data, size_t* DataSize)
{
	if ( Path == NULL ) { return MAXSI_ERROR_INVALID_INPUT; }

	MaxsiError	Result		=	MAXSI_ERROR_UNSPECIFIED;
	
	MaxsiHandle	FileHandle	=	FileSystem()->OpenFile(Path, MAXSI_FILE_READ | MAXSI_FILE_SEQUENTIAL);
	
	if ( FileHandle == NULL )
	{
		Result	=	MAXSI_ERROR_COULD_NOT_OPEN;
	}
	else
	{
		FileSize_t	FileSize		=	FileSystem()->GetFileSize(FileHandle);
		
		if ( FileSize > SIZE_MAX )
		{
			Result	=	MAXSI_ERROR_FILE_TOO_BIG;
		}
		else
		{
			if ( FileSize == 0 )
			{
				Result			=	MAXSI_ERROR_SUCCESS;
				*Data			=	NULL;
				*DataSize		=	0;
			}
			else
			{
				BYTE*	Buffer			=	new BYTE[FileSize+1];

				if ( Buffer != NULL )
				{
					size_t		ReadResult	=	FileSystem()->ReadFile(FileHandle, Buffer, (size_t)FileSize);
				
					if ( ReadResult != 0 )
					{
						Buffer[FileSize]	=	0;
						Result				=	MAXSI_ERROR_SUCCESS;
						*Data				=	Buffer;
						*DataSize			=	FileSize;
					}
					else
					{
						delete[]	Buffer;	// Failure
					}
				}
				else
				{
					Result	=	MAXSI_ERROR_MEM_ALLOC_FAIL;
				}
			}
		}

		FileSystem()->CloseFile(FileHandle);
	}

	return Result;
}

#if 0
int ReadAsciiTextFileSimple(MESTR* Path, char** Data, size_t* DataSize)
{
	if ( Path == NULL ) { return 1; }

	int		Result		=	1;
	
	MaxsiHandle	FileHandle	=	FileSystem()->OpenFile(Path, MAXSI_FILE_READ | MAXSI_FILE_SEQUENTIAL);

	if ( FileHandle != NULL )
	{
		FileSize_t	FileSize		=	FileSystem()->GetFileSize(FileHandle);

		if ( FileSize < SIZE_MAX )
		{
			if ( FileSize == 0 )
			{
				Result			=	0;
				*Data			=	NULL;
				*DataSize		=	0;
			}
			else
			{
				char*	Buffer			=	new char[FileSize+1];

				if ( Buffer != NULL )
				{
					size_t	ReadResult	=	FileSystem()->ReadFile(FileHandle, (BYTE*)Buffer, (size_t)FileSize);
				
					if ( ReadResult != 0 )
					{
						Buffer[FileSize]=	0;
						Result			=	0;
						*Data			=	Buffer;
						*DataSize		=	FileSize;
					}
					else
					{
						 delete[]	Buffer;	// Failure
					}
				}
			}
		}

		FileSystem()->CloseFile(FileHandle);
	}

	return Result;
}
#endif

MaxsiError ReadTextFile(MESTR* Path, MESTR** Data, size_t* DataLength, StringType_t Encoding)
{
	BYTE*	RawData			=	NULL;
	size_t	RawDataSize		=	0;

	MaxsiError	ReadResult	=	ReadFileSimpleNullTerminated(Path, &RawData, &RawDataSize);

	if ( ReadResult != MAXSI_ERROR_SUCCESS ) { return ReadResult; }

	MAXSI_TODO("Add support for zero-length files!");

	StringType_t	StringEncoding	=	(Encoding != MAXSI_STR_DETECT) ? Encoding : GuessStringEncoding((BYTE*)RawData, RawDataSize+1);

	if ( StringEncoding == MAXSI_STR_SYSTEM_LOCALE )
	{
		*Data	=	CHAR2MESTR((char*)RawData);
	}
	else if ( StringEncoding == MAXSI_STR_ASCII )
	{
		*Data	=	ASCII2MESTR((ASCII_t*)RawData);
	}
	else if ( StringEncoding == MAXSI_STR_UTF_8 )
	{
		*Data	=	UTF82MESTR((UTF8_t*)RawData);
	}
	else if ( StringEncoding == MAXSI_STR_UTF_16_LE )
	{
		*Data	=	RecodeStringAsMESTR("UTF−16LE", RawData, RawDataSize + sizeof(UTF16_t));
	}
	else if ( StringEncoding == MAXSI_STR_UTF_16_BE )
	{
		*Data	=	RecodeStringAsMESTR("UTF−16BE", RawData, RawDataSize + sizeof(UTF16_t));
	}
	else if ( StringEncoding == MAXSI_STR_UTF_32_LE )
	{
		*Data	=	RecodeStringAsMESTR("UTF−32LE", RawData, RawDataSize + sizeof(UTF32_t));
	}
	else if ( StringEncoding == MAXSI_STR_UTF_32_BE )
	{
		*Data	=	RecodeStringAsMESTR("UTF−32BE", RawData, RawDataSize + sizeof(UTF32_t));
	}
	else
	{
		PrintOutput("Could not detect text encoding in ReadTextFile!\n");
	}

	if ( *Data == NULL ) { return MAXSI_ERROR_COULD_NOT_CONVERT_TEXT; }
	if ( DataLength ) { *DataLength = MESTRLEN(*Data); }
	if ( RawData ) { delete[] RawData; }

	return MAXSI_ERROR_SUCCESS;;
}

bool CreateFolderRecursively(MESTR* Directory)
{
	size_t			Len			=	MESTRLEN(Directory);

	for (size_t I = 0; I < Len; I++)
	{
		if (Directory[I] == '/' || Directory[I] == '\\')
		{
			MESTR* TempDir	=	SubString(0, I+1, Directory);

			if ( TempDir == NULL ) { return false; }

			if( FileSystem()->CreateFolder(TempDir) == false ) { delete[] TempDir; return false; }

			delete[] TempDir;
		}
	}

	return true;
}

MESTR* RemoveFilePathExt(MESTR* FilePath)
{
	if ( FilePath == NULL ) { return NULL; }

	size_t	LastDot		=	FindCharPrev(FilePath, '.', MESTRLEN(FilePath));
	size_t	LastFSlash	=	FindCharPrev(FilePath, '/', MESTRLEN(FilePath));
	size_t	LastBSlash	=	FindCharPrev(FilePath, '\\', MESTRLEN(FilePath));

	// No dots in this filepath!
	if ( LastDot == ULONG_MAX )
	{
		return BuildString(1, FilePath);
	}

	// No dots in the filename!
	if ( LastDot < LastFSlash && LastDot < LastBSlash )
	{
		return BuildString(1, FilePath);
	}

	// Simply cut the extension
	return SubString(0, LastDot, FilePath);
}

FileSize_t GetFilePathSize64(MESTR* FilePath)
{
	if ( FilePath == NULL ) { return FILESIZE_MAX; }
	
	MaxsiHandle	FileHandle	=	FileSystem()->OpenFile(FilePath, MAXSI_FILE_READ | MAXSI_FILE_SEQUENTIAL);
	
	if ( FileHandle != NULL)
	{
		FileSize_t	Result	=	FileSystem()->GetFileSize(FileHandle);

		FileSystem()->CloseFile(FileHandle);

		return Result;
	}

	return FILESIZE_MAX;
}

MESTR* PrintFileSizeShort(FileSize_t Input)
{
	size_t BinaryPrefixNum = 10;
	MESTR* BinaryPrefix[] = {_MESTR("B"), _MESTR("KiB"), _MESTR("MiB"), _MESTR("GiB"), _MESTR("TiB"), _MESTR("PiB"), _MESTR("EiB"), _MESTR("ZiB"), _MESTR("YiB"), _MESTR("MoFoB"), };

	size_t Power = 0;
	size_t Decimal = 0;

	while ( Input >= 1024) { Decimal = ((10*Input) / 1024) % 10; Input /= 1024; Power++; }

	if ( Power > BinaryPrefixNum ) { Power = BinaryPrefixNum; }

	size_t PrintValue = (size_t)Input;

	if ( Power == 0 )
	{
		return PrintString("%zu %MS", PrintValue, BinaryPrefix[Power]);
	}
	else
	{
		return PrintString("%zu.%zu %MS", PrintValue, Decimal, BinaryPrefix[Power]);
	}
}
	
EndMaxsiNamespace

